package threads;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.Enumeration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import constants.Conts;

import android.ioio.client.Main;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Log;

public class Utils {

	/**Flag to signify if the threads are allowed to run. */
	private boolean running = true;
	/**Flag to signify if the socket is still connected. */
	private boolean stillConnected = false;
	/**Thread for listening to the {@link #listeningSocket}. */
	private Thread listeningThread;
	/**Thread for sending the packets to the client with the {@link #sendSocket}. */
	private Thread sendingThread;
	/**Thread to set up the initial connection. */
	private Thread connectionThread;
	/**A queue to hold the information to send to he client. */
	private BlockingQueue<byte[]> sendingQueue;
	/**The input stream from the socket.*/
	private InputStream socketInput;
	/**The output stream to the socket. */
	private OutputStream socketOutput;

	private ServerSocket serverSocket;
	
	private Main host;
	public boolean camEnabled = false;

	public Utils(Main host){
		this.host = host;
		sendingQueue = new ArrayBlockingQueue<byte[]>(20);
		connectionThread = new Thread(connectionRunnable);
		connectionThread.start();
	}
	
	/**
	 * Send a single command to the client.
	 * @param command - the command to send. See {@link Conts} //TODO break into inner classes for types
	 */
	public void sendCommand(byte command){
		if(isConnected()){
			byte[] data = new byte[Conts.PacketSize.UTILS_CONTROL_PACKET_SIZE];
			data[0] = command;
			sendData(data);
		}
	}
	
	/**
	 * Send a byte[] of data to the client. Must be length specified in {@link Conts#UTILS_CONTROL_PACKET_SIZE}
	 * and start with a code.
	 * @param data - the byte[] of data to send.
	 * @return True if the data was added to the queue to send, else false.
	 */
	public boolean sendData(byte[] data){
		if(data.length != Conts.PacketSize.UTILS_CONTROL_PACKET_SIZE){
			return false;
		}
		sendingQueue.add(data);
		return true;
	}

	/**
	 * Is the Utils waiting for a ping from the client?
	 * @return True if so, false otherwise.
	 */
	public boolean isConnected(){
		return stillConnected;
	}

	/**
	 * Process the byte[] received from the client.
	 * @param data - the byte[] to process.
	 */
	private void processData(byte[] data){
		switch(data[0]){
		case Conts.UTILS_MESSAGE_TYPE_DRIVER_ERROR:
			System.out.println("Recieved IOIO error:"+data[1]);
			break;
		}
	}

	/**
	 * A runnable for the {@link #listeningThread} to run.
	 */
	private Runnable listenRunnable = new Runnable(){
		@Override
		public void run() {
			while(running && stillConnected){
				try {
					byte[] data = new byte[Conts.PacketSize.UTILS_CONTROL_PACKET_SIZE];
					socketInput.read(data);
					processData(data);
				} catch (IOException e) {
					stillConnected = false;
					host.printToLog("Lost connection.");
					e.printStackTrace();
				}
			}
		}
	};

	/**
	 * A runnable for the {@link #sendingThread} to run.
	 */
	private Runnable sendRunnable = new Runnable(){
		@Override
		public void run() {
			while(running && stillConnected){
				try {
					socketOutput.write(sendingQueue.take());
				} catch (InterruptedException e) {
					host.printToLog("Lost connection.");
					stillConnected = false;
					e.printStackTrace();
				}catch (IOException e) {
					host.printToLog("Lost connection.");
					stillConnected = false;
					e.printStackTrace();
				}
			}
		}
	};
	/**Runnable for {@link #connectionThread}. This accepts the first connection and
	 * starts listening/sending on it.*/
	private Runnable connectionRunnable = new Runnable() {
		@Override
		public void run() {
			InetAddress finalAddress = null;
			try {
				try {
					URL whatismyip = new URL("http://automation.whatismyip.com/n09230945.asp");
//					BufferedReader in = new BufferedReader(new InputStreamReader(whatismyip.openStream()));
//
//					String ip = in.readLine();
//					host.printToLog("ip1 = "+ip);

					WifiManager myWifiManager = (WifiManager) host.getSystemService(host.WIFI_SERVICE);
					WifiInfo myWifiInfo = myWifiManager.getConnectionInfo();
					int ipAddress = myWifiInfo.getIpAddress();
					host.printToLog("ip2 = "+android.text.format.Formatter.formatIpAddress(ipAddress));
					int reverseIp = Integer.reverseBytes(ipAddress);
					
					Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
					outer:
					while(interfaces.hasMoreElements()){
						NetworkInterface network = interfaces.nextElement();
						Enumeration<InetAddress> addresses = network.getInetAddresses();
						while(addresses.hasMoreElements()){
							InetAddress address = addresses.nextElement();
							int iip = byteArrayToInt(address.getAddress(), 0);
							
							if(iip == ipAddress || iip == reverseIp){
								finalAddress = address;
								break outer;
							}
						}
					}
				} catch (MalformedURLException e) {
					e.printStackTrace();
				}catch (IOException e) {
					e.printStackTrace();
				}
				serverSocket = new ServerSocket(Conts.Ports.UTILS_INCOMMING_PORT, 0, finalAddress);
				host.printToLog("Utils wait.");
				startComms(serverSocket.accept());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	};
	/**Start the communication. Get the streams from the socket and start the threads. */
	private void startComms(Socket socket){
		try {
			socketInput = socket.getInputStream();
			socketOutput = socket.getOutputStream();

			stillConnected = true;
			host.printToLog("Utils connected.");

			listeningThread = new Thread(listenRunnable);
			sendingThread = new Thread(sendRunnable);

			listeningThread.start();
			sendingThread.start();
			
			while(stillConnected){
				Thread.sleep(1000);
				sendCommand(Conts.UtilsCodes.UTILS_CONNECTION_TEST);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public static int[] getErrorBits(int error){
		int[] bits = new int[9];
		
		if(error<0){
			bits[7] = 1;
			error = -error;
			error=128-error;
			bits[8]=128+error;
		}else{
			bits[8]=error;
		}
		
		while(error != 0){
			if(error >= 128){
				error-=128;
				bits[7] = 1;
			}else if(error >= 64){
				error-=64;
				bits[6] = 1;
			}else if(error >= 32){
				error-=32;
				bits[5] = 1;
			}else if(error >= 16){
				error-=16;
				bits[4] = 1;
			}else if(error >= 8){
				error-=8;
				bits[3] = 1;
			}else if(error >= 4){
				error-=4;
				bits[2] = 1;
			}else if(error >= 2){
				error-=2;
				bits[1] = 1;
			}else if(error == 1){
				error-=1;
				bits[0] = 1;
			}
		}
		
		return bits;
	}
	
	private int byteArrayToInt(byte[] arr, int offset) {
	    if (arr == null || arr.length - offset < 4)
	        return -1;
	 
	    int r0 = (arr[offset] & 0xFF) << 24;
	    int r1 = (arr[offset + 1] & 0xFF) << 16;
	    int r2 = (arr[offset + 2] & 0xFF) << 8;
	    int r3 = arr[offset + 3] & 0xFF;
	    return r0 + r1 + r2 + r3;
	}
}
